/**
 * Copyright (c) 2010 Simple Graph Tools contributors. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * $Id$
 */
package org.sgt.transformations.graph2testdef;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.sgt.coredef.Chain;
import org.sgt.coredef.Condition;
import org.sgt.coredef.Constraint;
import org.sgt.coredef.CoredefFactory;
import org.sgt.coredef.DescribedObject;
import org.sgt.coredef.Description;
import org.sgt.coredef.Edge;
import org.sgt.coredef.Graph;
import org.sgt.coredef.Node;
import org.sgt.coredef.ProxyEdge;
import org.sgt.coredef.ProxyNode;
import org.sgt.coredef.TextualDescription;
import org.sgt.coredef.util.GraphHelper;
import org.sgt.coredef.util.GraphSequencer;
import org.sgt.coredef.util.IGraphSequence;
import org.sgt.coredef.util.IGraphSequencerPolicy;
import org.sgt.coredef.util.IGraphWalkerPolicy;
import org.sgt.coredef.util.NodeHelper;
import org.sgt.coredef.util.Route;
import org.sgt.testdef.Step;
import org.sgt.testdef.TestCase;
import org.sgt.testdef.TestCondition;
import org.sgt.testdef.TestConstraint;
import org.sgt.testdef.TestPlan;
import org.sgt.testdef.TestSuite;
import org.sgt.testdef.TestdefFactory;
import org.sgt.transformations.Logger;
import org.sgt.transformations.TransformationException;

/**
 * An abstract implementation of {@link Graph} to {@link TestPlan} basic
 * transformation. Its purpose is to simplify implementers work in giving
 * default implementation for the generic logic of the transformation
 * as defined in the {@link IGraph2TestPlanTransformation} interface).
 * It caches <UL>
 * <LI>the {@link Graph} to transform (as unique input)</LI>
 * <LI>and the transformed {@link TestPlan} (as it's unique output)</LI>
 * <LI>a {@link IGraphWalkerPolicy}</LI>
 * <LI>and a {@link IGraphSequencerPolicy} are the parameters of this
 * transformation.</LI>
 * </UL>
 * The parameters default to null and must be set by concrete
 * implementations before this Class'{@link #prepare()} or {@link #process()}
 * methods are called.
 * <BR/>
 * Pre-processing consist in the validation of the {@link Graph} as defined in {@link #validateSourceGraph(Graph)}.
 * Post-processing in checking that a non null {@link TestPlan} was obtained.
 * <BR/>
 * This transformation is to be extended by all transformations of
 * concrete {@link Graph}s into {@link TestPlan}. It will perform all
 * generic operations of sequencing {@link Graph}s into a series of test steps
 * taking into account the re-assign of {@link  Constraint}s and {@link Condition}s
 * on the appropriate {@link Steps}. 
 * @author BUTTIGHOFFER-A
 *
 */
public abstract class AbstractGraph2TestPlanTransformation<SourceGraph extends Graph> implements
		IGraph2TestPlanTransformation<SourceGraph> {

	/**
	 * The key for input "sourceGraph" (the {@link Graph}
	 * to transform to a {@link TestPlan}).
	 */
	public static final String SOURCE_GRAPH_KEY = "sourceGraph";
	
	/**
	 * Our (here unique) input : an {@link Graph}.
	 * Should be accessed via {@link #getInputs()}
	 * and {@link #setInputs(Map)} by extending Classes
	 * under the key {@link #SOURCE_GRAPH_KEY}
	 * or directly {@link #getSourceGraph()}.
	 */
	private SourceGraph sourceGraph;
	
	/**
	 * The key for output "testPlan" (the {@link TestPlan}
	 * resulting from the transformation of the input {@link Graph}).
	 */
	public static final String TEST_PLAN_KEY = "testPlan";
	
	/**
	 * Our (here unique) output : a {@link TestPlan}
	 * Should be accessed via {@link #getOutputs()}
	 * by extending Classes under the key {@link #TEST_PLAN_KEY}
	 * or directly {@link #getTestPlan()}.
	 */
	private TestPlan testPlan;
	
	/**
	 * The key for parameter "walkingPolicy" (the {@link IGraphWalkerPolicy}
	 * used by this transformation).
	 */
	public static final String WALKING_POLICY_KEY = "walkingPolicy";
	
	/**
	 * Our walking policy
	 * is an {@link IGraphWalkerPolicy} and one of the
	 * transformation's parameters under the key {@link #WALKING_POLICY_KEY}.
	 * <BR/>
	 * Defaults to null and should be set by concrete
	 * implementations via {@link #setParameters(Map)}
	 * prior to calling this Class'{@link #prepare()}.
	 * @see IGraphWalkerPolicy
	 */
	private IGraphWalkerPolicy walkingPolicy 
		= null;

	/**
	 * The key for parameter "sequencingPolicy" 
	 * (the {@link IGraphSequencerPolicy} used by this transformation).
	 */
	public static final String SEQUENCING_POLICY_KEY = "sequencingPolicy";
	
	/**
	 * Our sequencing policy
	 * is an {@link ActionPlanGraphSequencerPolicy}.
	 * Defaults to null and should be set by concrete
	 * implementations via {@link #setParameters(Map)}
	 * prior to calling this Class'{@link #prepare()}.
	 * @see IGraphSequencerPolicy
	 */
	private IGraphSequencerPolicy sequencingPolicy 
		= null;

	/**
	 * Throws {@link TransformationException} if
	 * sourceGraph is undefined or invalid,
	 * walkingPolicy or sequencingPolicy are not instances of
	 * {@link IGraphWalkerPolicy} or {@link IGraphSequencerPolicy}
	 * respectively.
	 * <BR/>
	 * <EM>!! Implementers should override to set correct parameters
	 * before calling this method !!</EM>
	 * @see org.sgt.transformations.ITransformation#prepare()
	 */
	@Override
	public void prepare() throws TransformationException {
		//Validate our sourceGraph
		if (sourceGraph == null ||!validateSourceGraph(sourceGraph)){
				throw new TransformationException(
						"Invalid Graph for Transformation operation");
		}
		if (!(walkingPolicy instanceof IGraphWalkerPolicy)){
			throw new TransformationException("No walking policy defined");
		}
		if (!(sequencingPolicy instanceof IGraphSequencerPolicy)){
			throw new TransformationException("No sequencing policy defined");
		}
	}

	/* (non-Javadoc)
	 * @see org.sgt.transformations.ITransformation#process()
	 */
	@Override
	public void process() throws TransformationException {
		// exploring graph (building Paths by Route)...
		Map<Route, Set<Chain>> paths = exploreGraph();
		if (paths.isEmpty()){
			Logger.log("No Paths found in : " + sourceGraph);
			throw new TransformationException("No Paths found in sourceGraph");
		}
		
		//if explore OK
		// sequencing graph (building sequence(s) by Route)...
		Map<Route, Set<EList<Node>>> seqMap = sequenceGraph(paths);

		if (seqMap == null || seqMap.isEmpty()){
			Logger.log("No Sequences found in : " + sourceGraph);
			throw new TransformationException("No Sequences found in sourceGraph");
		}

		//if sequences were found Prepare TestPlan...
		Logger.log("... building test plan from sequences ...");
		testPlan = initTestPlan();
		
		//Creating conditions from those in the Graph
		//keeping an Action2Test map of them.
		Map<Condition,TestCondition> graph2testConditions = new HashMap<Condition,TestCondition>();
		for (Condition condition : sourceGraph.getGraphConditions()){
			TestCondition testCond = TestdefFactory.eINSTANCE.createTestCondition();
			graph2testConditions.put(condition, testCond);
			testPlan.getConditions().add(testCond);
			testCond.setName(condition.getName());
			testCond.setSummary(condition.getSummary());
			if (condition.getDescription() instanceof TextualDescription){
				TextualDescription original = (TextualDescription)condition.getDescription();
				TextualDescription copy = CoredefFactory.eINSTANCE.createTextualDescription();
				copy.setExpression(original.getExpression());
				copy.setLanguage(original.getLanguage());
				testCond.setDescription(copy);
			}
		}

		//Creating constraints from those in the Graph
		//keeping an Action2Test map of them.
		Map<Constraint,TestConstraint> graph2testConstraints = new HashMap<Constraint,TestConstraint>();
		for (Constraint constraint : sourceGraph.getGraphConstraints()){
			TestConstraint testConst = TestdefFactory.eINSTANCE.createTestConstraint();
			graph2testConstraints.put(constraint, testConst);
			testPlan.getConstraints().add(testConst);
			testConst.setName(constraint.getName());
			testConst.setSummary(constraint.getSummary());
			if (constraint.getDescription() instanceof TextualDescription){
				TextualDescription original = (TextualDescription)constraint.getDescription();
				TextualDescription copy = CoredefFactory.eINSTANCE.createTextualDescription();
				copy.setExpression(original.getExpression());
				copy.setLanguage(original.getLanguage());
				testConst.setDescription(copy);
			}
		}
		
		//building TestSuites, Cases and Steps and adding them to TestPlan...
		for(Route route : seqMap.keySet()){
			TestSuite suite = initTestSuite(route);
			int nbCases = 0;
			for ( EList<Node> nodes : seqMap.get(route) ){
				nbCases = nbCases + 1;
				Logger.log(nodes.toString());
				TestCase tCase = mappNodesSequence(nodes
						, graph2testConditions
						, graph2testConstraints);
				tCase.setName("Alternative " + nbCases);
				suite.getCases().put(Integer.valueOf(nbCases), tCase);
			}
			suite.setSummary("TestSuite for " + suite.getName() + " includes " + nbCases +" cases");
			testPlan.getTestElements().add(suite);
		}
	}
	
	/* (non-Javadoc)
	 * @see org.sgt.transformations.ITransformation#postProcess()
	 */
	@Override
	public void postProcess() throws TransformationException {
		if (testPlan == null) {
			throw new TransformationException("No TestPlan");
		}
		if (sourceGraph == null){
			throw new TransformationException("No Graph");			
		}
	}

	/* (non-Javadoc)
	 * @see org.sgt.transformations.ITransformation#setInputs(java.util.Map)
	 */
	@Override
	@SuppressWarnings("unchecked")
	public void setInputs(Map<String, Object> inputs) {
		if (inputs != null && !inputs.isEmpty()){
			for (Map.Entry<String, Object> entry : inputs.entrySet()){
				if (SOURCE_GRAPH_KEY.compareTo(entry.getKey()) == 0){
					if (entry.getValue() instanceof EObject){
						EObject eObject = (EObject) entry.getValue();
						if (eObject instanceof Graph){
							sourceGraph = (SourceGraph) eObject;
							//deleting out outputs...
							testPlan = null;
						}
					}
					break;
				}
			}
		}
	}

	@Override
	public Map<String, Object> getInputs() {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put(SOURCE_GRAPH_KEY, sourceGraph);
		return map;
	}

	/* (non-Javadoc)
	 * @see org.sgt.transformations.ITransformation#getOutputs()
	 */
	@Override
	public Map<String, Object> getOutputs() {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put(TEST_PLAN_KEY, testPlan);
		return map;
	}

	/* (non-Javadoc)
	 * @see org.sgt.transformations.ITransformation#setParameters(java.util.Map)
	 */
	@Override
	public void setParameters(Map<String, Object> params) throws TransformationException {
		if (params == null 
				|| params.isEmpty() 
				|| params.get(WALKING_POLICY_KEY) == null
				|| params.get(SEQUENCING_POLICY_KEY) == null) 
			throw new TransformationException("Invalid parameters");
		Object wPObj = params.get(WALKING_POLICY_KEY);
		Object sPObj = params.get(SEQUENCING_POLICY_KEY);
		if (wPObj instanceof IGraphWalkerPolicy 
				&& sPObj instanceof IGraphSequencerPolicy){
			walkingPolicy = (IGraphWalkerPolicy)wPObj; 
			sequencingPolicy =(IGraphSequencerPolicy)sPObj; 
		} else {
			throw new TransformationException(
					"Invalid parameters : missing or invalid graph walker" +
					"or sequencer policies");
		}		
	}

	/* (non-Javadoc)
	 * @see org.sgt.transformations.ITransformation#getParameters()
	 */
	@Override
	public Map<String, Object> getParameters() {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put(WALKING_POLICY_KEY, walkingPolicy);
		map.put(SEQUENCING_POLICY_KEY, sequencingPolicy);		
		return map;
	}

	/* (non-Javadoc)
	 * @see org.sgt.transformations.graph2testdef.IGraph2TestPlanTransformation#initTestSuite(Route)
	 */
	@Override
	public TestSuite initTestSuite(final Route route) {
		TestSuite suite = TestdefFactory.eINSTANCE.createTestSuite();
		Node sourceN = route.getSource();
		Node destN = route.getDestination();
		StringBuffer suiteName = new StringBuffer();
		if (sourceN instanceof DescribedObject){
			suiteName.append(((DescribedObject)sourceN).getName());
		} else {
			suiteName.append(sourceN.toString());
		}
		suiteName.append(" -> ");
		if (destN instanceof DescribedObject){
			suiteName.append(((DescribedObject)destN).getName());
		} else {
			suiteName.append(destN.toString());
		}
		suite.setName(suiteName.toString());
		return suite;
	}

	/* (non-Javadoc)
	 * @see org.sgt.transformations.graph2testdef.IGraph2TestPlanTransformation#mappNode(Node)
	 */
	@Override
	public Step mappNode(final Node node){
		Step step = TestdefFactory.eINSTANCE.createStep();
		Node original = node;
		if (node instanceof ProxyNode){
			original = ((ProxyNode) node).getRepresentedObject();
			if (original == null){
				//TODO it should NEVER be the case....
				original = node;
			}
		}
		if (original instanceof DescribedObject){
			DescribedObject descNode = (DescribedObject) original;	
			step.setName(descNode.getName());
			step.setSummary(descNode.getSummary());
			if (descNode.getDescription() != null){
				step.setDescription((Description) EcoreUtil.copy(descNode.getDescription()));
			}
		}
		return step;
	}
	
	/* (non-Javadoc)
	 * @see org.sgt.transformations.graph2testdef.IGraph2TestPlanTransformation#mappNodeSequences(EList,Map,Map)
	 */
	@Override
	public TestCase mappNodesSequence(final EList<Node> nodesSeq
			, Map<Condition,TestCondition> graph2testConditions
			, Map<Constraint,TestConstraint> graph2testConstraints
			){
		TestCase tCase = TestdefFactory.eINSTANCE.createTestCase();
		Map<Node, Step> node2steps = new HashMap<Node, Step>();
		if (nodesSeq != null && !nodesSeq.isEmpty()) {
			//tCase.setSummary(path.toString());
			// Loop on steps...
			int nbSteps = 0;
			Node prevNode = null;
			Node prevOrig = null;
			Node original = null;
			for (Node node : nodesSeq){
				if (node instanceof ProxyNode) {
					original = ((ProxyNode) node).getRepresentedObject();
				}
				//Build Step..
				Step step = mappNode(node);
				//update node2steps map...
				node2steps.put(node, step);
				//Add step to testCase...
				tCase.getSteps().put(Integer.valueOf(nbSteps), step);
				/*
				 * handling Conditions and constraints...
				 */
				Set<Condition> preConds = new HashSet<Condition>();
				Set<Condition> postConds = new HashSet<Condition>();
				Set<Constraint> consts = new HashSet<Constraint>();

				//Basic init...
				if (original != null) {
						consts.addAll(NodeHelper
								.getAllConstraints(original));
						preConds.addAll(NodeHelper
								.getAllPreConditions(original));
						postConds.addAll(NodeHelper
								.getAllPostConditions(original));
				}
				consts.addAll(NodeHelper.getAllConstraints(node));
				preConds.addAll(NodeHelper.getAllPreConditions(node));
				postConds.addAll(NodeHelper.getAllPostConditions(node));

				//Updating from previous node and connexions...
				if (prevNode != null) {
					EList<Node> investigate = new BasicEList<Node>();
					investigate.add(node);
					if (original != null) investigate.add(original);
					for (Node invNode : investigate) {
						// Are we connected to previous node via a real
						// graph edge (ie not as result of join/fork
						// decomposition)
						boolean direct = true;
						EList<Edge> ins = invNode.getIns();
						Edge edge = null;
						Edge origEdge = null;
						if (ins.size() == 1) {
							edge = ins.get(0);
							direct = false;
						} else {
							for (Edge curr : invNode.getIns()) {
								if (curr.getSource() == prevNode
										|| curr.getSource() == prevOrig) {
									edge = curr;
									direct = false;
									break;
								}
							}
						}
						if (edge instanceof ProxyEdge) {
							origEdge = ((ProxyEdge) edge)
									.getRepresentedObject();
						}

						if (direct) {
							preConds.addAll(prevNode.getPostConditions());
							if (original != null) {
								preConds.addAll(original.getPostConditions());
							}
						} else {
							preConds.addAll(edge.getPostConditions());
							preConds.addAll(edge.getPreConditions());
							consts.addAll(edge.getConstraints());
							if (origEdge != null) {
								preConds.addAll(origEdge.getPostConditions());
								preConds.addAll(origEdge.getPreConditions());
								consts.addAll(origEdge.getConstraints());
							}
						}
					}
				}

				//Translate node's constraints, pre and posts
				// to step's ones making use of node2steps map...				
				if (graph2testConstraints != null && !graph2testConstraints.isEmpty()){
					for (Constraint constraint : consts) {
						if (graph2testConstraints.containsKey(constraint)) {
							TestConstraint testConst = graph2testConstraints
									.get(constraint);
							step.getConstraints().add(testConst);
						}
					}
				}
				if (graph2testConditions != null && !graph2testConditions.isEmpty()){
					for (Condition condition : preConds) {
						if (graph2testConditions.containsKey(condition)) {
							TestCondition testCond = graph2testConditions
									.get(condition);
							step.getPreConditions().add(testCond);
						}
					}
				}
				
				/*
				 * Conditions and constraints handeled
				 * kepping up loop variables ...
				 */
				prevNode = node;
				if (prevNode instanceof ProxyNode){
					prevOrig = ((ProxyNode) prevNode).getRepresentedObject();
				}
				nbSteps = nbSteps + 1;
			}
			//
		}
		return tCase;
	}
	
	/* (non-Javadoc)test
	 * @see org.sgt.transformations.graph2testdef.IGraph2TestPlanTransformation#getGraph()
	 */
	@Override
	public SourceGraph getSourceGraph() {
		return sourceGraph;
	}

	/* (non-Javadoc)test
	 * @see org.sgt.transformations.graph2testdef.IGraph2TestPlanTransformation#getTestPlan()
	 */
	@Override
	public TestPlan getTestPlan() {
		return testPlan;
	}

	/**
	 * Explores the {@link Graph} to produce a Map of all {@link Chain}s
	 * for each {@link Route} in the {@link Graph} using the
	 * {@link #walkingPolicy} specified
	 * in this class.
	 * @see org.sgt.coredef.util.GraphHelper#GraphHelper(org.sgt.coredef.Graph, org.sgt.coredef.util.IGraphWalkerPolicy)
	 * @see org.sgt.coredef.util.GraphHelper#buildChains(Node, Node)
	 * @return the desired Map (may be empty but not null).
	 * @throws TransformationException if no walking policy is defined.
	 */
	public Map<Route, Set<Chain>> exploreGraph() throws TransformationException{
		if (walkingPolicy == null) throw new TransformationException("No walking policy defined for '" + getClass()
				+ "' Transformation");
		Logger.log("... retrieving Paths for Start-->End Routes using"
				+ walkingPolicy.getClass() + " ...");
		Map<Route, Set<Chain>> map = new HashMap<Route, Set<Chain>>();
		if(sourceGraph != null && walkingPolicy != null){
			for (Route route : computeRoutes()){
				GraphHelper helper = new GraphHelper(sourceGraph
						, walkingPolicy);
				EList<Chain> chainsL = helper.buildChains(
						route.getSource()
						, route.getDestination());
				Set<Chain> chainsSet = new HashSet<Chain>();
				for (Chain chain : chainsL){
					chainsSet.add(chain);
				}
				map.put(route, chainsSet);
			}
		}
		return map;
	}
	
	/**
	 * Sequences the {@link Graph} to produce a Map of all {@link Node}s
	 * sequences for each {@link Route} in the {@link Graph} using the
	 * {@link #sequencingPolicy} specified
	 * in this class.
	 * @see org.sgt.coredef.util.GraphSequencer#GraphSequencer(Map)
	 * @see org.sgt.coredef.util.GraphSequencer#sequence()
	 * @return the desired Map (may be empty or null).
	 * @throws TransformationException if {@link #sequencingPolicy} is not defined.
	 */
	public Map<Route, Set<EList<Node>>> sequenceGraph(final Map<Route, Set<Chain>> paths) throws TransformationException{
		if (sequencingPolicy == null) throw new TransformationException("No sequencing policy defined for '" + getClass()
				+ "' Transformation");
		Map<Route, Set<EList<Node>>> seqMap = null;
		if (paths != null){
			GraphSequencer sequencer = new GraphSequencer(paths);
			sequencer.setPolicy(sequencingPolicy);
			Logger.log("... sequencing graph for each of it's Routes using "
					+ sequencingPolicy.getClass() + " ...");
			IGraphSequence sequencedPlan = sequencer.sequence();
			seqMap = sequencedPlan.getEmbeddedObject();
		}
		return seqMap;
	}
	
	/**
	 * Prepares a new {@link TesPlan} to receive tests corresponding our
	 * sequenced graph.
	 * @param tpName name of the resulting {@link TestPlan}.
	 * @param tpSummary summary of the resulting {@link TestPlan}.
	 * @param tpCustomer the customer.
	 * @param sgProject the source graph project.
	 * @param sgProjectVersion the project version.
	 * @param tPVersion the resulting {@link TestPlan} version.
	 * @return the prepared testPlan (which will contain no {@link TestSuite}
	 * nor {@link TesSuite}
	 */
	public TestPlan initTestPlan(final String tpName, final String tpSummary
			, final String tpCustomer
			, final String sgProject
			, final String sgProjectVersion
			, final String tPVersion) {
		testPlan = TestdefFactory.eINSTANCE.createTestPlan();
		if (tpName != null || "".compareTo(tpName) != 0){
			testPlan.setName(tpName);
		}
		if (tpSummary != null && tpSummary.length() != 0){
			testPlan.setSummary(tpSummary);
		}
		if (tpCustomer != null && tpCustomer.length() != 0){
			testPlan.setCustomer(tpCustomer);
		}
		if (sgProject != null && sgProject.length() != 0){
			testPlan.setProject(sgProject);
		}
		if (sgProjectVersion != null && sgProjectVersion.length() != 0){
			testPlan.setProjectVersion(sgProjectVersion);
		}
		if (tPVersion != null && tPVersion.length() != 0){
			testPlan.setTestPlanVersion(tPVersion);
		}
		return testPlan;
	}


}
